﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Plan;

namespace PlanInterfejs
{
    /// <summary>
    /// Mapowanie danych wprowadzonych do encji bazy danych na parametry algorytmu.
    /// </summary>
    class AlgorithmParametersWrapper
    {
        plandbEncje db;
        // subjectIdMap mapuje ID w bazie -> ID w algorytmie
        // czyli jeśli subjectIdMap[i]=ID, to i-ID w algorytmie, a ID to ID w bazie.
        int[] subjectIdAlgToDbMap;
        Dictionary<int, List<int>> subjectIdDbToAlgMap = new Dictionary<int, List<int>>();

        public AlgorithmParametersWrapper(plandbEncje db)
        {
            this.db = db;
        }
        public AlgorithmParametersWrapper() : this (DAL.database)
        {

        }
        public ScheduleInstance GetAlgInstance()
        {
            var instance = new ScheduleInstance();
            
            // każde wystąpienie przedmiotu (np. każda matematyka)
            // to w alg. osobny przedmiot.
            int algSubjectCount = (from wm in db.Wymagane_Przedmioty
                                select wm.Ilosc).Sum();
            subjectIdAlgToDbMap = new int[algSubjectCount];

            instance.LessonsPerDay = db.Godziny_Zajec.Count();

            // uwaga: kolejność ma znaczenie
            AddClassrooms(instance);
            AddSubjects(instance);
            AddGroups(instance);
            AddTeachers(instance);

            return instance;
        }

        private void AddTeachers(ScheduleInstance instance)
        {
            foreach (var nauczyciel in db.Nauczyciel)
            {
                var t = new Teacher
                {
                    Id = nauczyciel.ID_Nauczyciel
                };
                var nauczane = nauczyciel.Nauczane_Przedmioty.Where(np => np.ID_Nauczyciel == nauczyciel.ID_Nauczyciel);
                var typyIds = from np in nauczyciel.Nauczane_Przedmioty
                              where np.ID_Nauczyciel == nauczyciel.ID_Nauczyciel
                              join typ in db.Przedmiot_Typy on np.ID_Przedmiot_Typy equals typ.ID_Przedmiot_Typy
                              select typ.ID_Przedmiot_Typy;
                foreach (int typId in typyIds)
                    t.Subjects.AddRange(subjectIdDbToAlgMap[typId]);
                instance.Teachers.Add(t);
            }
        }

        private void AddGroups(ScheduleInstance instance)
        {
            foreach (Grupa grupa in db.Grupa)
            {
                var g = new Group
                {
                    Id = grupa.ID_Grupa,
                };
                var typy = from wym in db.Wymagane_Przedmioty
                           where wym.Poziom_Grupy == grupa.Poziom_grupy
                           select new
                           {
                               TypId = wym.Przedmiot_Typy.ID_Przedmiot_Typy,
                               Ilosc = wym.Ilosc
                           };
                foreach (var item in typy)
                    for (int i = 0; i < item.Ilosc; i++)
                        g.Subjects.Add(Array.FindIndex(subjectIdAlgToDbMap, id => id == item.TypId) + i);

                instance.Groups.Add(g);
            }
        }

        private void AddSubjects(ScheduleInstance instance)
        {

            int subjectId = 0;
            var przedmioty = from wym in db.Wymagane_Przedmioty
                             select new
                             {
                                 TypId = wym.Przedmiot_Typy.ID_Przedmiot_Typy,
                                 Ilosc = wym.Ilosc
                             };
            foreach (var p in db.Przedmiot_Typy)
                subjectIdDbToAlgMap.Add(p.ID_Przedmiot_Typy, new List<int>());

            foreach (var p in przedmioty)
            {
                for (int i = 0; i < p.Ilosc; i++)
                {
                    // Zakładam, że jeśli w programie została ustawiona sala
                    // specjalnie do danego przedmiotu, to tenże przedmiot musi
                    // być w tej sali przeprowadzany. W algorytmie to są oddzielne przypadki,
                    // za przeprowadzenie zajęć w złej sali jest znacznie wyższa kara niż
                    // wykorzystanie sali "specjalnej" dla innego przedmiotu.
                    instance.Subjects.Add(new Subject
                    {
                        Id = subjectId,
                        NeedsDedicatedClassroom = instance.Classrooms.Any(room => room.DedicatedSubject == p.TypId)
                    });
                    subjectIdAlgToDbMap[subjectId] = p.TypId;
                    subjectIdDbToAlgMap[p.TypId].Add(subjectId);
                    subjectId++;
                }
            }
        }

        private void AddClassrooms(ScheduleInstance instance)
        {
            instance.Classrooms.AddRange(db.Sala.Select(sala =>
                new ClassRoom
                {
                    Id = sala.ID_Sala,
                    DedicatedSubject = sala.Przedmiot_Typy != null
                                    ? sala.Przedmiot_Typy.ID_Przedmiot_Typy
                                    : (int?)null
                }));
        }


        public SolutionAdnotations UpdateDatabase(ScheduleSolution solution)
        {
            var groupSubjects = solution.GetPlanCleared();
            ScheduleInstance inst = solution.Instance;
            var adnotations = new SolutionAdnotations
            {
                Shittness = solution.PlanShitness(true)
            };
            adnotations.KnownIssues = solution.Shits;

            foreach (var pz in db.Plan_Zajec)            
                db.Plan_Zajec.DeleteObject(pz);
            foreach (var pz in db.Zajecia)
                db.Zajecia.DeleteObject(pz);
            
            db.SaveChanges();
            foreach (Grupa grupa in db.Grupa)
            {
                foreach (GroupSubject gs in from gs in groupSubjects
                                            where inst.Groups[gs.Group].Id == grupa.ID_Grupa
                                            select gs)
                {
                    var planZajec = new Plan_Zajec();
                    planZajec.ID_Grupa = grupa.ID_Grupa;
                    var godz = (from godzina in db.Godziny_Zajec                                                 
                                                 orderby godzina.Czas_Poczatek
                                                 select godzina).Skip(gs.TimeOfDay).FirstOrDefault();
                    if (godz == null)
                        godz = db.Godziny_Zajec.First();
                    var dzien = db.Dzien.Where(d => d.Nr == gs.Day).FirstOrDefault();
                    if (dzien == null)
                        throw new ApplicationException("Algorytm zwrócił bezsensowny numer dnia");
                    //int mask = (1 << 16) - 1;
                    var zajecia = new Zajecia
                         {
                             ID_Nauczyciel = inst.Teachers[gs.Teacher].Id,
                             ID_Sala = inst.Classrooms[gs.Classroom].Id,
                             ID_Przedmiot_Typy = subjectIdAlgToDbMap[inst.Subjects[gs.Subject].Id],
                             ID_Godziny_Zajec = godz.NumerPorzadkowy,
                             ID_Dzien = dzien.ID_Dnia
                         };

                    db.Zajecia.AddObject(zajecia);
                    planZajec.Zajecia = zajecia;
                    db.Plan_Zajec.AddObject(planZajec);
                }
            }
            db.SaveChanges();
            return adnotations;
        }
    }
    class SolutionAdnotations
    {
        double shittness;

        public double Shittness
        {
            get { return shittness; }
            set { shittness = value; }
        }
        string knownIssues;

        public string KnownIssues
        {
            get { return knownIssues; }
            set { knownIssues = value; }
        }
    }
}
